/* caGenerator.c */

/*
 * Licensed to the ConArtist Development Team (CADT).
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * The CADT licenses this file to you under the Apache License, 
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <apr_hash.h>
#include <apr_strings.h>
#include <apr_thread_rwlock.h>
#include <apr_fnmatch.h>
#include <apr_file_io.h>
#include <apr_file_info.h>
#include <apr_pools.h>

#include "conartist.h"
#include "caPrivate.h"
#include "caGenerator.h"
#include "caLog.h"
#include "caStore.h"
#include "caMaps.h"


static char *replaceStrings(char *orig, const char *what, 
                            const char *with, apr_pool_t *p)
{
    size_t wlen = strlen(what);
    char *ptr = strstr(orig, what);
    char *newstr;

    if (!ptr)
        return orig;

    /* Remove duplicate '/' */
    if (*(ptr - 1) == '/' && with[0] == '/')
        with++;

    newstr = apr_pstrndup(p, orig, ptr - orig);
    newstr = apr_pstrcat(p, newstr, with, &ptr[wlen], NULL);
    return newstr;
}

apr_status_t caMapStoreCreate(struct caMapStore **stPtr, apr_pool_t *p)
{
    apr_status_t rv;
    struct caMapStore *store = apr_pcalloc(p, sizeof(*store));

    *stPtr = NULL;
    if (!store)
        return APR_ENOMEM;

    rv = apr_pool_create(&store->pool, p);
    if (rv != APR_SUCCESS)
        return rv;

    store->mapHash = apr_hash_make(store->pool);
    store->uriHash = apr_hash_make(store->pool);
    if (!store->mapHash || !store->uriHash)
        return APR_EINVAL;

    rv = apr_thread_rwlock_create(&store->rwLock, store->pool);
    if (rv != APR_SUCCESS)
        return rv;

    *stPtr = store;
    return APR_SUCCESS;
}

/* Insert a map structure into the map store, choosing either the
 * URI hash or the simpler map hash.
 */
apr_status_t caGeneratorAddMap(struct caURIMap *mp, struct caGenerator *gen)
{
    struct caMapStore *ms = gen->mapStore;
    apr_status_t rv = apr_thread_rwlock_wrlock(ms->rwLock);
    if (rv != APR_SUCCESS)
        return rv;
    if (mp->URI) {
        apr_hash_set(ms->uriHash, mp->URI, APR_HASH_KEY_STRING, mp);
        caLog(CA_LOG_INFO, "Added mapping for URI '%s' to generator '%s'",
              mp->URI, gen->name);
    } else {
        char *str = NULL;
        rv = apr_filepath_merge(&str, (mp->path ? mp->path : "/"),
                                mp->filename, 0, ms->pool);
        if (rv == APR_SUCCESS) {
            apr_hash_set(ms->mapHash, str, APR_HASH_KEY_STRING, mp);
            caLog(CA_LOG_INFO, "Added mapping for Map '%s' to generator '%s'",
                  str, gen->name);
        }
    }
    apr_thread_rwlock_unlock(ms->rwLock);
    return rv;
}

struct caURIMap *caGeneratorFindMap(struct caGenerator *gen, 
                                    const char *uri)
{
    struct caURIMap *mp = NULL;
    struct caMapStore *ms = gen->mapStore;
    apr_status_t rv;

    rv = apr_thread_rwlock_rdlock(ms->rwLock);
    if (rv != APR_SUCCESS)
        return NULL;

    /* First, look for an exact match... */
    mp = apr_hash_get(ms->uriHash, uri, APR_HASH_KEY_STRING);
    /* Next look for a match among the non-specific maps. */
    if (! mp) {
        apr_hash_index_t *hi = NULL;

        for (hi = apr_hash_first(ms->pool, ms->mapHash); hi; 
             hi = apr_hash_next(hi)) {
            const char *key = NULL;
            const void *keyPtr = NULL;
            char *fnPtr = (char *)uri;
            apr_size_t uLen = strlen(uri);
            void *mpPtr = NULL;

            apr_hash_this(hi, &keyPtr, NULL, &mpPtr);

            mp = (struct caURIMap *)mpPtr;
            key = (const char *)keyPtr;

            /* Do we have a path to match against? */
            if (mp->path) {               
                if (mp->pLen > uLen)
                    continue;
                if (strncmp(fnPtr, mp->path, mp->pLen))
                    continue;
                fnPtr = (char *)&uri[mp->pLen];
                if (*fnPtr == '/') fnPtr++;
            }

            rv = apr_fnmatch(mp->filename, fnPtr, APR_FNM_NOESCAPE);
            if (rv == APR_SUCCESS) {
                caLog(CA_LOG_DEBUG, "Found matching map (%s, %s) => %s", 
                      mp->path, mp->filename, uri);
                break;
            }
            mp = NULL;
        }
        if (!mp)
            caLog(CA_LOG_DEBUG, "No matching map found for '%s'", uri);
    }

    apr_thread_rwlock_unlock(ms->rwLock);
    return mp;
}

/* Clone the supplied caURIMap structure, setting the uri and generated 
 * filename to those supplied. Allocations are done from the mapStore
 * pool and the map is inserted if succesful.
 */
apr_status_t cloneURIMap(struct caURIMap **clonedMap, struct caURIMap *mp,
                         struct caGenerator *gen, const char *uri, 
                         const char *fn)
{
    struct caURIMap *mmp = NULL;
    apr_status_t rv = APR_SUCCESS;
    struct caMapStore *ms = gen->mapStore;

    mmp = (struct caURIMap *)apr_pcalloc(ms->pool, sizeof(*mmp));
    if (!mmp)
        return APR_ENOMEM;

    /* Copy only the information we need... */
    mmp->deps = mp->deps;
    mmp->URI = apr_pstrdup(ms->pool, uri);
    mmp->pi = mp->pi;
    if (fn) {
        mmp->generatedFn = apr_pstrdup(ms->pool, fn);
        caLog(CA_LOG_DEBUG, "Found an exact URI match");
    } else {
        if (mp->pi) {
            mmp->flags |= CAM_CACHE;
            rv = caRecurseCheckDirectory(apr_table_get(gen->cfg, "CachePath"),
                                         uri);
            if (rv != APR_SUCCESS)
                return rv;
            rv = apr_filepath_merge(&mmp->generatedFn,
                                    apr_table_get(gen->cfg, "CachePath"),
                                    (uri[0] == '/' ? &uri[1] : uri), 0, 
                                    ms->pool);
            if (rv != APR_SUCCESS)
                return rv;
        }
    }

    rv = caGeneratorAddMap(mmp, gen);
    if (rv != APR_SUCCESS)
        return rv;

/*
    if (st)
        storeNotifyAdd(st, fn, CAN_ALL, invalidateMP, mpp);
*/

    *clonedMap = mmp;
    return rv;
}

void copyOutputInfo(struct caContent *req, struct caURIMap *map,
                    char *oBuffer, apr_size_t oLen)
{
    req->filename = apr_pstrdup(req->pool, map->generatedFn);
    req->fLen = map->fLen;
    if (map->pi)
        req->contentType = apr_pstrdup(req->pool, 
                                       map->pi->pipeline->contentType);
    /* TODO - encoding? */
    if (oBuffer) {
        req->data = oBuffer;
        req->dataLen = oLen;
    }
}

char *mapOutputFilename(struct caURIMap *mp, const char *uri, apr_pool_t *p)
{
caLog(CA_LOG_DEBUG, "mapOutputFilename (%s) => path = %s, filename = %s, output = %s",
      uri, mp->path, mp->filename, mp->output);

    if (strstr(mp->output, "$(")) {
        char *fn = NULL;
        char *thefn = NULL, *ext, *qry;
        char *rqst = (char *)uri;

        thefn = strrchr(uri, '/');
        if (thefn)
            ext = strchr(thefn, '.');
        else
            ext = strchr(uri, '.');
        qry = strrchr(uri, '?');
        if (thefn) thefn++;
        if (ext || qry)
            thefn = apr_pstrndup(p, thefn, (ext ? ext : qry) - thefn);
        if (ext) ext++;

        if (ext && qry)
            ext = apr_pstrndup(p, ext, qry - ext);
        if (qry)
            rqst = apr_pstrndup(p, uri, qry - uri);

        fn = replaceStrings(mp->output, "$(FILENAME)", thefn, p);
        fn = replaceStrings(fn, "$(REQUEST)", rqst, p);
        fn = replaceStrings(fn, "$(PATH)", mp->path, p);
        fn = replaceStrings(fn, "$(EXT)", ext, p);
        fn = replaceStrings(fn, "$(QUERY_STR)", qry, p);
        return fn;
    }
    return mp->output;
}
